#include "DeviceWin32.h"
#include "Sol9Types.h"
#include "IEventReceiver.h"
#include "rect2d.h"
#include "Win32Painter.h"
#include "D3D9Renderer.h"

namespace sol9
{
namespace device
{

	__implementClass(DeviceWin32, DeviceStub);
	DeviceMapList DeviceWin32::s_listDeviceMaps;
	std::map<UINT, eMouseInputEvent> DeviceWin32::s_mapMouseMessage;

	DeviceWin32::DeviceWin32(const SDeviceParameters& Params)
		: DeviceStub(Params), m_hWnd(nullptr)
		, m_pPainter(nullptr), m_pRenderer(nullptr)
	{
		if (!_createWindow())
			m_bClose = true;
		
		SetActiveWindow(m_hWnd);
		SetForegroundWindow(m_hWnd);

		s_mapMouseMessage[WM_MOUSEMOVE] = EMIE_MouseMove;
		s_mapMouseMessage[WM_LBUTTONDOWN] = EMIE_LButtonDown;
		s_mapMouseMessage[WM_LBUTTONUP] = EMIE_LButtonUp;
		s_mapMouseMessage[WM_LBUTTONDBLCLK] = EMIE_LButtonDClick;
		s_mapMouseMessage[WM_RBUTTONDOWN] = EMIE_RButtonDown;
		s_mapMouseMessage[WM_RBUTTONUP] = EMIE_RButtonUp;
		s_mapMouseMessage[WM_RBUTTONDBLCLK] = EMIE_RButtonDClick;
		s_mapMouseMessage[WM_MBUTTONDOWN] = EMIE_MButtonDown;
		s_mapMouseMessage[WM_MBUTTONUP] = EMIE_MButtonUp;
		s_mapMouseMessage[WM_MBUTTONDBLCLK] = EMIE_MButtonDClick;
		s_mapMouseMessage[WM_MOUSEWHEEL] = EMIE_MouseWheel;
	}

	DeviceWin32::~DeviceWin32()
	{
		close();

		if (m_pPainter)
			m_pPainter->detach();

		if (m_pRenderer)
			m_pRenderer->detach();
	}
	
	bool DeviceWin32::run()
	{
		if (!DeviceStub::run())
			return false;

		MSG msg;

		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.hwnd == m_hWnd)
				Procedure(m_hWnd, msg.message, msg.wParam, msg.lParam);
			else
				DispatchMessage(&msg);

			if (msg.message == WM_QUIT)
				m_bClose = true;
		}

		//if (!m_bClose)
		//{
		//}

		return !m_bClose;
	}

	void DeviceWin32::close()
	{
		DeviceStub::close();

		MSG msg;

		PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
		PostQuitMessage(0);
		PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE);
		DestroyWindow(m_hWnd);
		m_bClose = true;
	}

	void DeviceWin32::yield()
	{
		Sleep(1);
	}

	void DeviceWin32::sleep(u32 nTimeMs)
	{
		Sleep(nTimeMs);
	}

	graphics::IPainter* DeviceWin32::createPainter(graphics::ePainterTypes eType, graphics::eColorFormat eFormat)
	{
		switch (eType)
		{
		case graphics::EPT_Win32 :
			m_pPainter = new graphics::Win32Painter(m_hWnd, m_Parameters.nWindowSize, eFormat);
			m_pPainter->attach();
			break;

		default :
			m_pPainter = NULL;
			break;
		}

		return m_pPainter;
	}

	graphics::IRenderer* DeviceWin32::createRenderer()
	{
		graphics::SRendererParameters params;
		params.nWindowSize = m_Parameters.nWindowSize;
		params.bFullscreen = m_Parameters.bFullscreen;

		m_pRenderer = new graphics::D3D9Renderer(m_hWnd, params);
		m_pRenderer->attach();

		return m_pRenderer;
	}

	void DeviceWin32::minimizeWindow()
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof(WINDOWPLACEMENT);

		GetWindowPlacement(m_hWnd, &wp);
		wp.showCmd = SW_SHOWMINNOACTIVE;
		SetWindowPlacement(m_hWnd, &wp);
	}

	void DeviceWin32::maximizeWindow()
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof(WINDOWPLACEMENT);

		GetWindowPlacement(m_hWnd, &wp);
		wp.showCmd = SW_SHOWMAXIMIZED;
		SetWindowPlacement(m_hWnd, &wp);
	}

	void DeviceWin32::restoreWindow()
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof(WINDOWPLACEMENT);

		GetWindowPlacement(m_hWnd, &wp);
		wp.showCmd = SW_SHOWNORMAL;
		SetWindowPlacement(m_hWnd, &wp);
	}

	void DeviceWin32::setWindowCaption(const std::tstring& strCaption)
	{
		SetWindowText(m_hWnd, strCaption.c_str());
	}

	bool DeviceWin32::isWindowActive() const
	{
		return GetActiveWindow() == m_hWnd;
	}

	bool DeviceWin32::isWindowFocused() const
	{
		return GetFocus() == m_hWnd;
	}

	bool DeviceWin32::isWindowMinimized() const
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof(WINDOWPLACEMENT);

		if (!GetWindowPlacement(m_hWnd, &wp))
			return false;

		return (wp.showCmd & SW_SHOWMINIMIZED) != 0;
	}

	bool DeviceWin32::_createWindow()
	{
		HINSTANCE hInstance = GetModuleHandle(NULL);

		WNDCLASS WndClass;
		WndClass.style = CS_HREDRAW | CS_VREDRAW;
		WndClass.lpfnWndProc = Procedure;
		WndClass.cbClsExtra = 0;
		WndClass.cbWndExtra = 0;
		WndClass.hInstance  = hInstance;
		WndClass.hIcon = (HICON)LoadImage(hInstance, _T("sol9.ico"), IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
		WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
		WndClass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
		WndClass.lpszClassName = m_Parameters.strWindowName.c_str();
		WndClass.lpszMenuName = m_Parameters.strWindowName.c_str();

		if (!RegisterClass(&WndClass))
			return false;

		/////
		DWORD style = WS_OVERLAPPEDWINDOW;

		if (m_Parameters.bFullscreen)
			style = WS_POPUP;

		core::rect2di rtClient(core::vector2di(0, 0), (core::dimension2di)m_Parameters.nWindowSize);
		AdjustWindowRectEx((LPRECT)&rtClient, style, FALSE, 0);

		core::dimension2di realSize = rtClient.getSize();

		core::position2di windowPos;
		windowPos.x = (GetSystemMetrics(SM_CXSCREEN) - realSize.width) / 2;
		windowPos.y = (GetSystemMetrics(SM_CYSCREEN) - realSize.height) / 2;

		if (windowPos.x < 0)
			windowPos.x = 0;

		if (windowPos.y < 0)
			windowPos.y = 0;

		if (m_Parameters.bFullscreen)
			windowPos.set(0, 0);
		////

		m_hWnd = CreateWindowEx(0, m_Parameters.strWindowName.c_str(), m_Parameters.strWindowName.c_str(), style,
			windowPos.x, windowPos.y, realSize.width, realSize.height, NULL, NULL, hInstance, NULL);

		if (!m_hWnd)
			return false;

		ShowWindow(m_hWnd, SW_SHOW);
		UpdateWindow(m_hWnd);

		MoveWindow(m_hWnd, windowPos.x, windowPos.y, realSize.width, realSize.height, TRUE);

		s_listDeviceMaps.push_back(SDeviceMap(m_hWnd, this));

		return true;
	}

	graphics::IPainter* DeviceWin32::getPainter() const
	{
		return m_pPainter;
	}

	graphics::IRenderer* DeviceWin32::getRenderer() const
	{
		return m_pRenderer;
	}

	eDeviceTypes DeviceWin32::getDeviceType() const
	{
		return EDT_Win32;
	}
	
	LRESULT CALLBACK DeviceWin32::Procedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		SEvent event;
		DeviceWin32* pDevice;

		switch (message)
		{
		case WM_PAINT :
			{
				PAINTSTRUCT ps;
				BeginPaint(hWnd, &ps);
				EndPaint(hWnd, &ps);
			}
			return 0;

		case WM_KEYDOWN :
		case WM_KEYUP :
		case WM_SYSKEYDOWN :
		case WM_SYSKEYUP :
			{
				BYTE keys[256];

				event.type = EET_KeyInput;
				event.key.key = (eKeyCode)wParam;
				event.key.pressedKey = (message == WM_KEYDOWN || message == WM_SYSKEYDOWN);

				if (event.key.key == KEY_SHIFT)
				{
					event.key.key = (eKeyCode)MapVirtualKey(((lParam >> 16) & 255), MAPVK_VSC_TO_VK_EX);
				}

				if (event.key.key == KEY_CONTROL)
				{
					event.key.key = (eKeyCode)MapVirtualKey(((lParam >> 16) & 255), MAPVK_VSC_TO_VK_EX);

					if (lParam & 0x1000000)
						event.key.key = KEY_RCONTROL;
				}

				if (event.key.key == KEY_MENU)
				{
					event.key.key = (eKeyCode)MapVirtualKey(((lParam >> 16) & 255), MAPVK_VSC_TO_VK_EX);

					if (lParam & 0x1000000)
						event.key.key = KEY_RMENU;
				}

				GetKeyboardState(keys);

				event.key.pressedShift = ((keys[VK_SHIFT] & 0x80) != 0);
				event.key.pressedControl = ((keys[VK_CONTROL] & 0x80) != 0);

				if ((keys[VK_MENU] & 0x80) != 0)
					event.key.pressedControl = false;

				pDevice = getDeviceFromHWND(hWnd);

				if (pDevice)
					pDevice->postEventFromUser(event);

				if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP)
					break;
				else
					return 0;
			}

		case WM_MOUSEMOVE :
		case WM_LBUTTONDOWN :
		case WM_LBUTTONUP :
		case WM_LBUTTONDBLCLK :
		case WM_RBUTTONDOWN :
		case WM_RBUTTONUP :
		case WM_RBUTTONDBLCLK :
		case WM_MBUTTONDOWN :
		case WM_MBUTTONUP :
		case WM_MBUTTONDBLCLK :
		case WM_MOUSEWHEEL :
			{
				/// don't be checking buttonup & buttondown in realtime.
				/// should modify routine.
				event.type = EET_MouseInput;
				event.mouse.event = s_mapMouseMessage[message];
				event.mouse.x = (short)LOWORD(lParam);
				event.mouse.y = (short)HIWORD(lParam);
				event.mouse.w = 0;
				event.mouse.pressedShift = ((LOWORD(wParam) & MK_SHIFT) != 0);
				event.mouse.pressedControl = ((LOWORD(wParam) & MK_CONTROL) != 0);
				
				if (event.mouse.event == EMIE_MouseWheel)
				{
					POINT p = {0, 0};
					ClientToScreen(hWnd, &p);

					event.mouse.x -= p.x;
					event.mouse.y -= p.y;
					event.mouse.w = ((f32)(GET_WHEEL_DELTA_WPARAM(wParam))) / (f32)WHEEL_DELTA;
				}

				pDevice = getDeviceFromHWND(hWnd);

				if (pDevice)
					pDevice->postEventFromUser(event);

				return 0;
			}

		case WM_DESTROY :
			PostQuitMessage(0);
			return 0;
		}

		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	
	DeviceWin32* DeviceWin32::getDeviceFromHWND(HWND hWnd)
	{
		DeviceMapIterator iter = s_listDeviceMaps.begin();
		
		while (iter != s_listDeviceMaps.end())
		{
			if (iter->hWnd == hWnd)
				return iter->pDevice;

			++iter;
		}

		return NULL;
	}

} // namespace device
} // namespace sol9